package com.googlecode.dgwt.client.dojo.data.model;

import java.util.Collection;

import com.google.gwt.dom.client.Element;
import com.googlecode.dgwt.client.dijit.View;
import com.googlecode.dgwt.client.dojo.Console;
import com.googlecode.dgwt.client.dojo.data.base.Store;
import com.googlecode.dgwt.client.dojo.data.format.ObjectFormatter;
import com.googlecode.dgwt.client.dojo.data.format.ValueFormatter;
import com.googlecode.dgwt.client.dojo.exception.AttachException;
import com.googlecode.dgwt.client.dojo.exception.ValidationException;
import com.googlecode.dgwt.client.dojo.util.NativeList;

/**
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
public abstract class StoreColumnBase<Item, Type> implements StoreColumn<Item, Type> {

    private boolean hasUserFormatter;
    private Collection<StoreColumnBase<Item, Type>> proxyList;
    private ValueFormatter<Type> formatter = ObjectFormatter.get();
    private StoreModel<Item,StoreColumnBase<Item,Type>> model;
    private int columnIndex = -1;
    private boolean readOnly;
    
    
    public StoreColumnBase() {
    }

    public StoreColumnBase(boolean readOnly) {
        setReadOnly(readOnly);
    }
    
    public StoreColumnBase(ValueFormatter<Type> formatter) {
        setFormatter(formatter);
    }
    
    public StoreColumnBase(boolean readOnly, ValueFormatter<Type> formatter) {
        setReadOnly(readOnly);
        setFormatter(formatter);
    }
    
    protected final boolean isModelAttached() {
        return model != null;
    }
    
    @SuppressWarnings("unchecked")
    protected <Model extends StoreModel<Item,?>> Model getModel() {
        assert (isModelAttached()) : "Column has no parent model attached";
        return (Model) model;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void onAttach(StoreModel<Item, ?> model) throws AttachException {
        if (this.model != null) {
            this.columnIndex = -1;
            this.model.getColumns().remove(this);
            Console.warning(this, 
                    "Can't add the same column to two store model." +
                    "Use a ProxyColumn instead.");
        }
        this.model = (StoreModel) model;
    }

    @Override
    public void onDetach(StoreModel<Item, ?> model) {
        this.model = null;
        this.columnIndex = -1;
    }

    public int getColumnIndex() {
        if (columnIndex == -1 && model != null) {
            columnIndex = model.getColumns().indexOf(this);
        }
        return columnIndex;
    }   
    
    public void setReadOnly(boolean readOnly) {
        if (this.readOnly != readOnly) {
            this.readOnly = readOnly;
            onColumnChanged();
        }
    }

    public boolean isReadOnly() {
        return readOnly;
    }
    
    public void refreshValues() {
        if (model instanceof StoreModelBase<?,?>) {
            ((StoreModelBase<Item,?>)getModel()).onColumnRefresh(this);
        }
        if (proxyList != null){
            for (StoreColumnBase<Item, Type> column : proxyList){
                column.refreshValues();
            }
        }
    }
    
    protected void onColumnChanged() {
        int columnIndex = getColumnIndex();
        if (columnIndex != -1 && model != null) {
            model.getColumns().set(columnIndex, this);
        }
        if (proxyList != null){
            for (StoreColumnBase<Item, Type> column : proxyList){
                column.onColumnChanged();
            }
        }
    } 
    
    protected void onItemValueModified(Item item) {
        for (Store<Item> store : getModel().getAttachedStores()) {
            if (store.modified(item)) {
                store.refreshValue(item, getColumnIndex());
            }
        }        
        if (proxyList != null){
            for (StoreColumnBase<Item, Type> column : proxyList){
                column.onItemValueModified(item);
            }
        }
    }
    
    private Collection<StoreColumnBase<Item, Type>> getProxyList() {
        if (proxyList == null){
            proxyList = new NativeList<StoreColumnBase<Item,Type>>();
        }
        return proxyList;
    }

    private boolean recursionCheck(StoreColumnBase<Item, Type> proxy) {
        if (this.equals(proxy)){
            return false;
        }
        if (proxyList != null){
            for (StoreColumnBase<Item, Type> child : proxyList){
                if (!child.recursionCheck(proxy)){
                    return false;
                }
            }
        }
        return true;
    }

    public boolean addProxy(StoreColumnBase<Item, Type> column){
        if (column != null){
            return recursionCheck(column) && getProxyList().add(column);
        }
        return false;
    }
    
    public boolean removeProxy(Object column){
        if (proxyList != null){
            return proxyList.remove(column);
        }
        return false;
    }
    
    public boolean hasProxy(Object column){
        return proxyList != null && proxyList.contains(column);
    }
    
    @Override
    public void onUpdateColumn(View view, Element element, Item item) {
        element.setInnerText(getDisplayValue(item));
    }

    @Override
    public void onFormatColumn(Element element, Item item) {
    }

    @Override
    public void onRegisterParent(View view, boolean registered) {
    }

    @Override
    public void setDisplayValue(Item item, String value) throws ValidationException {
        setValue(item, getFormatter().unformatValue(value));
    }
    
    @Override
    public String getDisplayValue(Item item) {
        return getFormatter().formatValue(getValue(item));
    }

    @SuppressWarnings("unchecked")
    public void setFormatter(ValueFormatter<? extends Type> formatter) {
        if (formatter == null){
            formatter = getDefaultValueFormatter();
            hasUserFormatter = false;
        } else {
            hasUserFormatter = !formatter.equals(ObjectFormatter.get());
        }
        if (!formatter.equals(this.formatter)) {
            this.formatter = (ValueFormatter<Type>) formatter;
            refreshValues();
        }
    }

    public ValueFormatter<Type> getFormatter() {
        return formatter;
    }

    protected void updateFormatter(){
        if (!hasUserFormatter){
            setFormatter(null);
        }
    }
    
    protected ValueFormatter<? extends Type> getDefaultValueFormatter(){
        return ObjectFormatter.get(getColumnType());
    }

    public boolean isWriteable() {
        return !isReadOnly() && !getFormatter().equals(ObjectFormatter.get());
    }

    @Override
    public final Type getValue(Item item) {
        return onGetValue(item);
    }
 
    @Override
    public final boolean setValue(Item item, Type value) {
        if (!isReadOnly()) {
            onSetValue(item, value);
            onItemValueModified(item);
            return true;
        }
        return false;
    }

    protected abstract Type onGetValue(Item item);
    
    protected abstract void onSetValue(Item item, Type value);

}
